package com.ydj.framework.third.wxpay;

import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.ydj.framework.third.wxpay.protocol.WxGenerateNfcSchemeReq.JumpWxaBean;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.ydj.framework.exception.CommonException;
import com.ydj.framework.exception.DistributedLockException;
import com.ydj.framework.redis.ExtRedisTemplate;
import com.ydj.framework.redis.RedisDb;
import com.ydj.framework.redis.RedisKey;
import com.ydj.framework.third.wxpay.protocol.*;
import com.ydj.framework.third.wxpay.redprotocol.RdsWxMpAccessToken;
import com.ydj.framework.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Component
public class WxCommonManager {

    @Value("${wx.mp.appId}")
    private String mpAppId;

    @Value("${wx.mp.appSecret}")
    private String mpAppSecret;

    @Autowired
    private ExtRedisTemplate redisTemplate;
    @Autowired
    private RedisDb redisDb;

    public String getToken() {
        try {
            return redisDb.lock("RdsWxMpAccessTokenLock", () -> {
                String accessTokenKey = RedisKey.getMpAccessToken();
                RdsWxMpAccessToken rdsWxMpAccessToken = redisTemplate.getIfExceptionDelete(accessTokenKey);

                if (rdsWxMpAccessToken != null && rdsWxMpAccessToken.getExpires().getTime() > System.currentTimeMillis()) {
                    String accessToken = rdsWxMpAccessToken.getAccessToken();
                    if (accessToken != null) {
                        return accessToken;
                    }
                }

                // 参考文档 https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html
                String urlFmt = "https://api.weixin.qq.com/cgi-bin/token?grant_type=%s&appid=%s&secret=%s";
                String url = String.format(urlFmt, "client_credential", mpAppId, mpAppSecret);

                log.info("微信：提交【刷新Token】。Get请求:{}", url);
                HttpResponse receiveRes = HttpRequest.get(url)
                        .timeout(10000)
                        .execute();
                String receive = receiveRes.body();
                log.info("微信：提交【刷新Token】。Get请求:{}, 响应:{}", url, receive);
                WxGetTokenRes res = JsonUtils.getGson().fromJson(receive, WxGetTokenRes.class);
                if (res == null) {
                    return null;
                }

                // 得到响应后，保存到redis中
                rdsWxMpAccessToken = new RdsWxMpAccessToken()
                        .setAccessToken(res.getAccess_token())
                        .setExpires(new Date(System.currentTimeMillis() + res.getExpires_in() / 2 * 1000));
                redisTemplate.opsForValue().set(accessTokenKey, rdsWxMpAccessToken, res.getExpires_in(), TimeUnit.SECONDS);

                return rdsWxMpAccessToken.getAccessToken();
            });
        } catch (CommonException | DistributedLockException e) {
            log.error("报错了", e);
            return null;
        }
    }

    /**
     * 微信，根据code换取openId
     */
    public WxGetOpenIdByCodeRes getOpenIdByCode(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + mpAppId + "&secret=" + mpAppSecret + "&js_code=" + code + "&grant_type=authorization_code";

        HttpResponse receiveRes = HttpRequest.get(url)
                .timeout(120000)
                .execute();
        String receive = receiveRes.body();
        log.info("微信：【根据code换取open id】，响应:{}", receive);
        WxGetOpenIdByCodeRes res = new Gson().fromJson(receive, WxGetOpenIdByCodeRes.class);
        if (res == null) {
            log.error("微信：【根据code换取open id】。返回空协议");
            return null;
        }
        if (res.getErrcode() != null && res.getErrcode() != 0) {
            log.error("微信：【根据code换取open id】。协议告知失败. 错误码{}, 原因是{}", res.getErrcode(), res.getErrmsg());
            return null;
        }
        return res;
    }

    /**
     * 获取微信手机号
     *
     * @return
     */
    public WxGetUserPhoneNumberRes.PhoneInfoBean getUserPhoneNumber(String code) {
        // 参考文档 https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html
        String urlFmt = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s";
        String url = String.format(urlFmt, getToken());

        WxGetUserPhoneNumberReq req = new WxGetUserPhoneNumberReq()
                .setCode(code);

        String body = JsonUtils.toJson(req);
        HttpResponse receiveRes = HttpRequest.post(url)
                .body(body)
                .timeout(120000)
                .execute();
        String receive = receiveRes.body();
        log.info("微信：【获取微信手机号】，响应:{}", receive);
        WxGetUserPhoneNumberRes res = new Gson().fromJson(receive, WxGetUserPhoneNumberRes.class);
        if (res == null) {
            log.error("微信：【获取微信手机号】。返回空协议");
            return null;
        }
        if (res.getErrcode() != null && res.getErrcode() != 0) {
            log.error("微信：【获取微信手机号】。协议告知失败. 错误码{}, 原因是{}", res.getErrcode(), res.getErrmsg());
            return null;
        }
        if (res.getPhone_info() == null) {
            log.error("微信：【获取微信手机号】。phoneInfo部分为空");
            return null;
        }
        return res.getPhone_info();
    }

    /**
     * 获取 NFC 的小程序 scheme
     */
    public String generateNfcScheme(String physCardNumber) {
        // 参考文档 https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-scheme/generateNFCScheme.html
        String urlFmt = "https://api.weixin.qq.com/wxa/generatenfcscheme?access_token=%s";
        String url = String.format(urlFmt, getToken());

        WxGenerateNfcSchemeReq req = new WxGenerateNfcSchemeReq()
                .setJump_wxa(new JumpWxaBean()
                        .setPath("pages/share/share")
                        .setQuery("id=" + physCardNumber)
                        .setEnv_version("release")
                )
                .setModel_id("Ka_qpQx3cmGTAxdPnknBlw")
                .setSn(physCardNumber);

        // 针对等于号，不要做转义
        Gson gson = new GsonBuilder()
                .disableHtmlEscaping()
                .create();

        String body = gson.toJson(req);
        HttpResponse receiveRes = HttpRequest.post(url)
                .body(body)
                .timeout(120000)
                .execute();
        String receive = receiveRes.body();
        log.info("微信：【获取 NFC 的小程序 scheme】，卡号{}, 响应:{}", physCardNumber, receive);
        WxGenerateNfcSchemeRes res = new Gson().fromJson(receive, WxGenerateNfcSchemeRes.class);
        if (res == null) {
            log.error("微信：【获取 NFC 的小程序 scheme】。返回空协议");
            return null;
        }

        // 重复申请
        if (res.getErrcode() != null && res.getErrcode() == 9800010) {
            String regex = "schema:\\s*(weixin://dl/business/\\?t=[^\\s]+)";
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(res.getErrmsg());
            if (matcher.find()) {
                String extracted = matcher.group(1);
                return extracted;
            }
        }

        if (res.getErrcode() != null && res.getErrcode() != 0) {
            log.error("微信：【获取 NFC 的小程序 scheme】。协议告知失败. 错误码{}, 原因是{}", res.getErrcode(), res.getErrmsg());
            return null;
        }

        if (res.getOpenlink() == null) {
            log.error("微信：【获取 NFC 的小程序 scheme】。open link为空");
            return null;
        }
        return res.getOpenlink();
    }
}
